Various ScalarValue numeric method fixes & refactors (especially decimal)#23631
Various ScalarValue numeric method fixes & refactors (especially decimal)#23631Jefffrey wants to merge 9 commits into
ScalarValue numeric method fixes & refactors (especially decimal)#23631Conversation
Since we already have access to these constant lookup tables, its more efficient to index into them rather than calculating via `checked_pow()` at runtime.
There's a side-effect of some tests failing so I removed them. I think this is fine since it doesn't make sense to have a `ScalarValue` method where we request a value of a certain type but can get a different value type out.
Seems like this was just a simple omission
For one/negative one, if the scale equals the precision then all values are to the right of the decimal point meaning we actually cannot represent a value of 1, so this could have caused some subtle errors. Similar for ten, we need at least 2 digits left of the decimal point, that is (p - s) >= 2, to represent a ten value.
Using macros here to generate these tables instead of manually enumerating the values into the code itself; for i256 its a bit complicated since it doesn't have native support so can't use direct multiplication as it doesn't have const support. Used Codex in this case, but added a unit test to ensure the constant values generated are correct.
I noticed we didn't have support for `Float16`, `Decimal32` and `Decimal64` here; instead of just adding them here, I figured it would be easier to plumb in the `ScalarValue` code to reduce duplication and ensure there's less opportunities for subtle omissions like this.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #23631 +/- ##
==========================================
+ Coverage 80.67% 80.68% +0.01%
==========================================
Files 1086 1087 +1
Lines 366800 366789 -11
Branches 366800 366789 -11
==========================================
+ Hits 295912 295943 +31
+ Misses 53260 53212 -48
- Partials 17628 17634 +6 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
msrv failure related to |
| // | ||
| // This is mainly a shortcut for not needing to manually list out each value | ||
| // anyway. | ||
| let mut values = [i256::ONE; _]; |
There was a problem hiding this comment.
I think this is basically const mul? I also ran into that recently elsewhere, so figured I'll make a PR - apache/arrow-rs#10363
There was a problem hiding this comment.
this would be very helpful indeed 🙏
| if let Expr::Literal(sv, _) = s | ||
| && sv.data_type().is_numeric() | ||
| { | ||
| // unwrap safe since numeric types always have a 0 value | ||
| sv == &ScalarValue::new_zero(&sv.data_type()).unwrap() | ||
| } else { | ||
| false | ||
| } | ||
| } | ||
|
|
||
| pub fn is_one(s: &Expr) -> bool { | ||
| match s { | ||
| Expr::Literal(ScalarValue::Int8(Some(1)), _) | ||
| | Expr::Literal(ScalarValue::Int16(Some(1)), _) | ||
| | Expr::Literal(ScalarValue::Int32(Some(1)), _) | ||
| | Expr::Literal(ScalarValue::Int64(Some(1)), _) | ||
| | Expr::Literal(ScalarValue::UInt8(Some(1)), _) | ||
| | Expr::Literal(ScalarValue::UInt16(Some(1)), _) | ||
| | Expr::Literal(ScalarValue::UInt32(Some(1)), _) | ||
| | Expr::Literal(ScalarValue::UInt64(Some(1)), _) => true, | ||
| Expr::Literal(ScalarValue::Float32(Some(v)), _) if *v == 1. => true, | ||
| Expr::Literal(ScalarValue::Float64(Some(v)), _) if *v == 1. => true, | ||
| Expr::Literal(ScalarValue::Decimal128(Some(v), _p, s), _) => { | ||
| *s >= 0 | ||
| && POWS_OF_TEN | ||
| .get(*s as usize) | ||
| .map(|x| x == v) | ||
| .unwrap_or_default() | ||
| } | ||
| Expr::Literal(ScalarValue::Decimal256(Some(v), _p, s), _) => { | ||
| *s >= 0 | ||
| && match i256::from(10).checked_pow(*s as u32) { | ||
| Some(res) => res == *v, | ||
| None => false, | ||
| } | ||
| } | ||
| _ => false, | ||
| if let Expr::Literal(sv, _) = s | ||
| && sv.data_type().is_numeric() | ||
| // there are edge cases like negative scale decimals not being able to | ||
| // create a one value so this can fail | ||
| && let Ok(one) = ScalarValue::new_one(&sv.data_type()) |
There was a problem hiding this comment.
My only worry here is that we made an effort (I think mostly around stats) to avoid allocating new ScalarValue instances, but this is just a lot nicer.
There was a problem hiding this comment.
is the worry about the stack space it takes or the code being invoked to create the instance? curious to learn more about the context here
There was a problem hiding this comment.
I went back and found the PRs I remembered:
Seems like the issue was more about aggregating multiple ScalarValue instances, which used to fallback to arrow based aggregations that required heap allocation, so I think this is perfectly fine.
AdamGS
left a comment
There was a problem hiding this comment.
I think a bunch of this code is mine so I figured I'll review it 😅
It all makes sense, just two small comments
Which issue does this PR close?
N/A
Rationale for this change
I noticed there were some subtle errors with how decimals were handled in scalar values, and also opportunity to remove power calls in favour of precomputed constant tables. Also filling out some other missing support.
What changes are included in this PR?
I recommend looking at the commits as they are self contained with detailed messages for each.
Are these changes tested?
Yes
Are there any user-facing changes?
No